using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.Win32;

namespace Tsanie.ShellExtension.Shell {

    public delegate void OnDefaultAction(ShellActionType t);

    [ComVisible(true)]
    [Guid("0979A716-5B17-4e0c-BF72-EDAE9EA592DF")]
    public class ShellFolder : IPersistFolder, IShellFolder {
        public ShellView m_view = null;
        internal byte[] m_pidlData = null;
        internal MMC.MyDataObject m_mdo = null;

        private string m_workingDir = "";
        private string m_currentItem = "";
        internal Form m_form = null;

        private IntPtr m_parentWindow = IntPtr.Zero;
        private string m_parentWindowText = "";

        #region - Public Property -

        public string ParentWindowText { get { return m_parentWindowText; } }
        public IntPtr ParentWindow { get { return m_parentWindow; } }
        private string WorkingDirectory {
            get { return m_workingDir; }
            set {
                if (value[value.Length - 1] != '\\') {
                    m_workingDir = value + "\\";
                } else {
                    m_workingDir = value;
                }
            }
        }
        public string SelectedItem {
            get { return m_currentItem; }
            set {
                if (value.IndexOf("\\") == -1) {
                    m_currentItem = m_workingDir + value;
                } else {
                    m_currentItem = value;
                }
                ICommDlgBrowser cdlg = null;
                try {
                    cdlg = (ICommDlgBrowser)Marshal.GetObjectForIUnknown(Marshal.GetComInterfaceForObject(this.m_view.m_shell, typeof(ICommDlgBrowser)));
                } catch {
                    return;
                }
                cdlg.OnStateChange(this.m_view, 0x00000002);
            }
        }

        #endregion

        internal OnDefaultAction OnAction = null;

        public ShellFolder() {
            this.m_view = new ShellView(this);
            m_mdo = new MMC.MyDataObject(0, new byte[] { });
        }

        [DllImport("user32.dll")]
        internal static extern IntPtr SetParent(IntPtr hWndChild, IntPtr hWndNewParent);
        [DllImport("user32.dll")]
        internal static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
        [DllImport("user32.dll")]
        internal static extern int GetWindowLong(IntPtr hWnd, int nIndex);
        [DllImport("user32.dll")]
        internal static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int X, int Y, int cx, int cy, uint uFlags);
        [DllImport("shell32.dll")]
        internal static extern bool SHGetPathFromIDList(IntPtr pidl, System.Text.StringBuilder pszPath);
        [DllImport("kernel32.dll")]
        internal static extern bool IsBadWritePtr(IntPtr lp, int ucb);
        [DllImport("kernel32.dll")]
        internal static extern bool IsBadReadPtr(IntPtr lp, int ucb);
        [DllImport("shlwapi.dll")]
        internal static extern bool PathMakeSystemFolder(string pszPath);

        private byte[] PidlToBytes(IntPtr pidl) {
            // compute the total size..
            ushort total = 0;
            ushort cb = (ushort)Marshal.ReadInt16(pidl);
            while (cb != 0) {
                total += cb;
                cb = (ushort)Marshal.ReadInt16(pidl, total);
            }
            // allocate...
            byte[] ret = new byte[total];
            // read...
            Marshal.Copy(pidl, ret, 0, total);
            // return...
            return ret;
        }

        #region - IPersistFolder Member -

        void IPersistFolder.GetClassID(ref Guid pClassID) {
            Common.OutputDebugString("IPersistFolder.GetClassID(ref Guid)");
            pClassID = new Guid("0979A716-5B17-4e0c-BF72-EDAE9EA592DF");
        }
        long IPersistFolder.Initialize(IntPtr pidl) {
            Common.OutputDebugString("IPersistFolder.Initialize(pidl = {0})", pidl.ToInt32());
            this.m_pidlData = this.PidlToBytes(pidl);
            this.m_mdo = new MMC.MyDataObject(0, this.m_pidlData);
            return 0;
        }

        #endregion

        #region - IShellFolder Member -

        void IShellFolder.ParseDisplayName(IntPtr hwnd, IntPtr pbc, string pszDisplayName, out uint pchEaten, out IntPtr ppidl, ref uint pdwAttributes) {
            pchEaten = (uint)pszDisplayName.Length;
            pdwAttributes = 0;
            ppidl = this.m_mdo.m_pidl;
        }

        void IShellFolder.EnumObjects(int hwnd, int grfFlags, ref IEnumIDList ppenumIDList) {
            ppenumIDList = null;
        }
        void IShellFolder.BindToObject(IntPtr pidl, IntPtr pbc, ref Guid riid, ref IntPtr ppv) {
            IntPtr iunkMe = Marshal.GetIUnknownForObject(this);
            Marshal.QueryInterface(iunkMe, ref riid, out ppv);
        }
        void IShellFolder.BindToStorage(IntPtr pidl, IntPtr pbc, ref Guid riid, ref IntPtr ppv) {
            ppv = IntPtr.Zero;
        }
        void IShellFolder.CompareIDs(int lParam, IntPtr pidl1, IntPtr pidl2) { }
        long IShellFolder.CreateViewObject(IntPtr hwndOwner, ref Guid riid, out IntPtr ppv) {
            long hr = 0x00;
            ppv = IntPtr.Zero;
            if (riid == typeof(IShellView).GUID)
                ppv = Marshal.GetComInterfaceForObject(this.m_view, typeof(IShellView));
            if (ppv.ToInt32() == 0)
                hr = 0x8000FFFFL;
            this.m_parentWindow = hwndOwner;
            System.Text.StringBuilder sb = new System.Text.StringBuilder(1024);
            HookStuff.GetWindowText(this.m_parentWindow, sb, 1024);
            this.m_parentWindowText = sb.ToString();
            return hr;
        }
        long IShellFolder.GetAttributesOf(uint cidl, IntPtr apidl, ref int rgfInOut) {
            if (cidl == 1) {
                rgfInOut &= 0x40000000;
            } else {
                rgfInOut = 0;
            }
            return 0;
        }
        void IShellFolder.GetUIObjectOf(IntPtr hwndOwner, uint cidl, IntPtr apidl, ref Guid riid, ref uint rgfReserved, ref IntPtr ppv) {
            ppv = IntPtr.Zero;
        }
        void IShellFolder.GetDisplayNameOf(IntPtr pidl, uint uFlags, out STRRET pName) {
            pName.sString = this.m_workingDir;
            pName.uType = 0;
            if (pidl.ToInt32() == 0)
                throw (new COMException(null, 0x8000));
            short cookie = Marshal.ReadInt16(pidl, 2);
            if (cookie == 255) {
                pName.sString = m_currentItem;
            }
        }
        void IShellFolder.SetNameOf(IntPtr hwnd, IntPtr pidl, IntPtr pszName, long uFlags, out IntPtr ppidlOut) {
            ppidlOut = IntPtr.Zero;
        }

        #endregion

        public void DoDefaultAction() {
            ICommDlgBrowser cdlg = null;
            try {
                cdlg = (ICommDlgBrowser)Marshal.GetObjectForIUnknown(Marshal.GetComInterfaceForObject(this.m_view.m_shell, typeof(ICommDlgBrowser)));
            } catch {
                return;
            }
            cdlg.OnDefaultCommand(this.m_view);
        }

        [ComRegisterFunctionAttribute]
        public static void RegisterFunction(Type t) {
            try {
                Common.OutputDebugString("RegisterFunction():: {0}, {1}", t.FullName, t.GUID.ToString("B"));
                // add the correct things to the CLSID so the thing works as an extension
                using (RegistryKey kClass = Registry.ClassesRoot.OpenSubKey("CLSID\\{" + t.GUID.ToString() + "}", true)) {
                    RegistryKey ProgId = kClass.OpenSubKey("ProgId");
                    kClass.SetValue(null, (string)ProgId.GetValue(null));
                    ProgId.Close();
                    RegistryKey ShellFolder = kClass.CreateSubKey("ShellFolder");
                    ShellFolder.SetValue("Attributes", 0x78000040);
                    ShellFolder.SetValue("WantsFORPARSING", "");
                    ShellFolder.Close();
                    kClass.Close();
                }
                // add it to the approved list of extensions
                using (RegistryKey Approved = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true)) {
                    Approved.SetValue("{" + t.GUID.ToString() + "}", t.FullName);
                    Approved.Close();
                }
                Common.SHChangeNotify(Common.SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
            } catch (Exception e) {
                Common.OutputDebugString("RegisterFunction():: {0}, {1} - {2}", t.FullName, e.GetType().FullName, e.Message);
            }
        }

        [ComUnregisterFunctionAttribute]
        public static void UnregisterFunction(Type t) {
            using (RegistryKey CLSID = Registry.ClassesRoot.OpenSubKey("CLSID", true)) {
                CLSID.DeleteSubKeyTree("{" + t.GUID.ToString() + "}");
                CLSID.Close();
            }
            using (RegistryKey Approved = Registry.LocalMachine.OpenSubKey("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Shell Extensions\\Approved", true)) {
                Approved.DeleteValue("{" + t.GUID.ToString() + "}", false);
                Approved.Close();
            }
            Common.SHChangeNotify(Common.SHCNE_ASSOCCHANGED, 0, IntPtr.Zero, IntPtr.Zero);
        }

        protected void Setup(string WorkingDirectory, Shell.OnDefaultAction EventToFire, Form FormToShow) {
            this.WorkingDirectory = WorkingDirectory;
            this.OnAction += EventToFire;
            this.m_form = FormToShow;
        }
    }

    [ComVisible(false)]
    public enum ShellActionType {
        Open = 0,
        Save = 1,
        Unknown = 2
    }

}
